package copyengine.utils.tick
{
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import copyengine.utils.GeneralUtils;
	import copyengine.utils.cache.object.ICEMovieClip;
	import copyengine.utils.debug.CELog;
	import copyengine.utils.tick.node.AnimationCacheMovieclipTickObject;
	import copyengine.utils.tick.node.AnimationMovieClipTickObject;
	import copyengine.utils.tick.node.AnimationTickObjectNodeBasic;
	import copyengine.utils.tick.node.CountTickObjectNode;
	import copyengine.utils.tick.node.OldAnimationTickObjectNode;
	import copyengine.utils.tick.node.TickObjectNode;
	import copyengine.utils.tick.node.TweenEffectTickObjectNode;

	/**
	 * GlobalTick is convenient utils class, it's usually use in this condition
	 *
	 * 1) have an effect MovieClip , we only want it paly certain time . and then remove form it parent.
	 *
	 * 2) hava an class need to update it property , but that property maybe will be changed in the same tick.
	 *     so in case to optimize performance , that property need to change in next frame.
	 *
	 * 3) need to play an effect in dialog show hide
	 *
	 * @author Tunied
	 *
	 */
	public class GlobalTick
	{
		private static var _instance:GlobalTick;

		public static function get instance():GlobalTick
		{
			if (_instance == null)
			{
				_instance = new GlobalTick();
			}
			return _instance;
		}

		private var holdTimer:Timer;
		private var holdMc:Sprite;

		private var lastTickTime:int;

		private var firstTickObjectNode:TickObjectNode; //double linked list , hold the like head

		public function GlobalTick()
		{
			lastTickTime = getTimer();
			//FPS 24 理论timer设置应该为42
			//FPS=24 --> 1000/24 ~= 42
			//但是设置42后 每次Tick时间约为86ms
			//但是设置40ms却比较准确，具说和Flash跑到模型有关
			holdTimer = new Timer(42);
			GeneralUtils.addTargetEventListener(holdTimer, TimerEvent.TIMER, tick);
			holdTimer.start();
		}

		/**
		 * after intervalTick , then will call the function back. use in case 2 (See GlobalTick)
		 *
		 * @param _f							每次回调函数（回调函数不接受参数）
		 *
		 * @param _tick			每次回调间隔Tick数
		 *
		 * @param _repeatTime			Tick重复次数
		 *
		 * @param _isSington				是否整个Tick队列中只能存在唯一的回调的函数
		 */
		public function callLaterAfterTickCount(_f:Function, _tick:int = 1, _repeatTime:int = 0, _isSington:Boolean = true):void
		{
			if (!_isSington || !isContainFunction(_f))
			{
				var tickNode:TickObjectNode = new CountTickObjectNode(_f, _tick, _repeatTime);
				addToTickQueue(tickNode);
			}
		}

		/**
		 * use the tick time to simulation timer ( use in scrollBar thumb move.)
		 */
		public function callLaterAfterTimerCount(_f:Function, _second:Number, _repeatTime:int = 0, _isSington:Boolean = true):void
		{
			if (!_isSington || !isContainFunction(_f))
			{
				var tickNode:TickObjectNode = new TickObjectNode(_f, _second * 1000, _repeatTime);
				addToTickQueue(tickNode);
			}
		}

		/**
		 * play an MoveClip assign time count. use in case 1 (See GlobalTick)
		 */
		private function doPlayMovieClip(_m:*, _f:Function, _repeatTime:int, _isSington:Boolean, _endAction:int, _mcTotalFrame:int):AnimationTickObjectNodeBasic
		{
			if (!_isSington || !isContainFunction(_f))
			{
				var tickNode:AnimationTickObjectNodeBasic;
				if (_m is MovieClip)
				{
					tickNode = new AnimationMovieClipTickObject(_m, _f, _repeatTime, _endAction, _mcTotalFrame)
				}
				else if (_m is ICEMovieClip)
				{
					tickNode = new AnimationCacheMovieclipTickObject(_m, _f, _repeatTime, _endAction, _mcTotalFrame)
				}
				else
				{
					CELog.err("GlobalTick->doPlayMovieClip _m type is not MovieClip or ICECacheMovieClip");
				}
				tickNode && addToTickQueue(tickNode);
				return tickNode;
			}
			return null;
		}

		/**
		 * <b>播放Mc特效并停止</b>
		 *
		 * @param _m							Mc(可以为MovieClip也可以为ICEMovieClip)
		 * @param _f							特效执行完毕后的回调
		 * @param _repeatTime			Mc特效执行次数
		 * @param _isSington				是否为单例，及在执行期间不再接受当前回调函数的新请求
		 * @param _mcTotalFrame		如果不指定TotalFrame则播放整个MovieClip,可以当Mc套Mc的情况，
		 * 												比如Actor，外帧数为Actor可进行的动作，第一帧进去后再取的帧数才是播放的帧数，
		 * 												此时直接取mc.totalFrame则仅能取到外帧数
		 * @return
		 *
		 */
		public function playAndStopMovieClip(_m:*, _f:Function = null, _repeatTime:int = 0, _isSington:Boolean = false, _mcTotalFrame:int = int.MIN_VALUE):AnimationTickObjectNodeBasic
		{
			return doPlayMovieClip(_m, _f, _repeatTime, _isSington, OldAnimationTickObjectNode.END_ACTION_STOP, _mcTotalFrame);
		}

		/**
		 * <b>播放Mc特效并删除</b>
		 *
		 * @param _m							Mc(可以为MovieClip也可以为ICEMovieClip)
		 * @param _f							特效执行完毕后的回调
		 * @param _repeatTime			Mc特效执行次数
		 * @param _isSington				是否为单例，及在执行期间不再接受当前回调函数的新请求
		 * @param _mcTotalFrame		如果不指定TotalFrame则播放整个MovieClip,可以当Mc套Mc的情况，
		 * 												比如Actor，外帧数为Actor可进行的动作，第一帧进去后再取的帧数才是播放的帧数，
		 * 												此时直接取mc.totalFrame则仅能取到外帧数
		 * @return
		 *
		 */
		public function playAndRemoveMovieClip(_m:*, _f:Function = null, _repeatTime:int = 0, _isSington:Boolean = false, _mcTotalFrame:int = int.MIN_VALUE):AnimationTickObjectNodeBasic
		{
			return doPlayMovieClip(_m, _f, _repeatTime, _isSington, OldAnimationTickObjectNode.END_ACTION_REMOVE, _mcTotalFrame);
		}

		/**
		 * <b>播放Mc特效并隐藏</b>
		 *
		 * @param _m							Mc(可以为MovieClip也可以为ICEMovieClip)
		 * @param _f							特效执行完毕后的回调
		 * @param _repeatTime			Mc特效执行次数
		 * @param _isSington				是否为单例，及在执行期间不再接受当前回调函数的新请求
		 * @param _mcTotalFrame		如果不指定TotalFrame则播放整个MovieClip,可以当Mc套Mc的情况，
		 * 												比如Actor，外帧数为Actor可进行的动作，第一帧进去后再取的帧数才是播放的帧数，
		 * 												此时直接取mc.totalFrame则仅能取到外帧数
		 * @return
		 *
		 */
		public function playAndInvisibleMovieClip(_m:*, _f:Function = null, _repeatTime:int = 0, _isSington:Boolean = false, _mcTotalFrame:int = int.MIN_VALUE):AnimationTickObjectNodeBasic
		{
			return doPlayMovieClip(_m, _f, _repeatTime, _isSington, OldAnimationTickObjectNode.END_ACTION_INVISIBLE, _mcTotalFrame);
		}

		/**
		 * <b>播放Mc特效,结束后无其他动作</b>
		 *
		 * @param _m							Mc(可以为MovieClip也可以为ICEMovieClip)
		 * @param _f							特效执行完毕后的回调
		 * @param _repeatTime			Mc特效执行次数
		 * @param _isSington				是否为单例，及在执行期间不再接受当前回调函数的新请求
		 * @param _mcTotalFrame		如果不指定TotalFrame则播放整个MovieClip,可以当Mc套Mc的情况，
		 * 												比如Actor，外帧数为Actor可进行的动作，第一帧进去后再取的帧数才是播放的帧数，
		 * 												此时直接取mc.totalFrame则仅能取到外帧数
		 * @return
		 *
		 */
		public function playMovieClip(_m:*, _f:Function = null, _repeatTime:int = 0, _isSington:Boolean = false, _mcTotalFrame:int = int.MIN_VALUE):AnimationTickObjectNodeBasic
		{
			return doPlayMovieClip(_m, _f, _repeatTime, _isSington, OldAnimationTickObjectNode.END_ACTION_DO_NOTHING, _mcTotalFrame);
		}

		/**
		 * some time use Tween is very difficulty to make some complex animation , in that case we can use MovieClip inside
		 * we play the tweenMC , and each tick copy the propery of tweenMc to targetMC(x,y,sacle,alpha,rotation).
		 * so to make an animation just make such MovieClipe
		 *
		 * WARNINIG:: the tweenMC must contain an child name "mc".
		 *
		 */
		public function playTweenEffect(_target:DisplayObject, _tweenMC:MovieClip, _endCallBackFunction:Function = null, _repeatTime:int = 0):void
		{
			var tickNode:TweenEffectTickObjectNode = new TweenEffectTickObjectNode(_target, _tweenMC, _endCallBackFunction, _repeatTime);
			addToTickQueue(tickNode);
		}

		private function tick(e:Event):void
		{
			var currentTime:int = getTimer();
			var elapseTime:int = currentTime - lastTickTime;
			lastTickTime = currentTime;
			var node:TickObjectNode = firstTickObjectNode;
			var nextNode:TickObjectNode;
			while (node != null)
			{
				nextNode = node.getNext() as TickObjectNode;
				if (node.isNeedRemove)
				{
					node.destory();
					removeFromTickQueue(node);
				}
				else
				{
					node.tick(elapseTime);
				}
				node = nextNode;
			}
		}

		public function removeTickNodeByFunction(_f:Function):void
		{
			if (_f != null)
			{
				var node:TickObjectNode = firstTickObjectNode;
				var nextNode:TickObjectNode;
				while (node != null)
				{
					nextNode = node.getNext() as TickObjectNode;
					if (node.tickFinishedCallBackFunction == _f)
					{
						node.isNeedRemove = true;
						node.tickFinishedCallBackFunction = null;
						break;
					}
					node = nextNode;
				}
			}
		}

		private function addToTickQueue(_tickNode:TickObjectNode):void
		{
			_tickNode.setNext(firstTickObjectNode);
			firstTickObjectNode = _tickNode;
		}

		private function removeFromTickQueue(_tickNode:TickObjectNode):void
		{
			if (firstTickObjectNode == _tickNode) // current Node is first node.
			{
				firstTickObjectNode = _tickNode.getNext() as TickObjectNode;
				_tickNode.setNext(null);
			}
			else
			{
				_tickNode.getPrevious().setNext(_tickNode.getNext());
			}
		}

		private function isContainFunction(_f:Function):Boolean
		{
			var node:TickObjectNode = firstTickObjectNode;
			var nextNode:TickObjectNode;
			while (node != null)
			{
				nextNode = node.getNext() as TickObjectNode;
				if (node.tickFinishedCallBackFunction == _f && !node.isNeedRemove)
				{
					return true;
				}
				node = nextNode;
			}
			return false;
		}

	}
}

